package com.bionic.banking.logic.controller;

import com.bionic.banking.core.entity.*;
import com.bionic.banking.core.enums.BankTransactionTypeEnum;
import com.bionic.banking.dao.IBankTransactionDao;
import com.bionic.banking.dao.classes.BankTransactionDaoImpl;
import com.bionic.banking.dao.connection.LoggerLoader;
import com.bionic.banking.logic.*;
import com.bionic.banking.logic.exception.*;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.*;
import org.apache.log4j.Logger;

public class BankTransactionController extends AbstractBean<BankTransaction>
        implements IBankTransactionController {

    private static final Logger logger = LoggerLoader.getLogger(BankTransactionController.class);

    @Override
    protected IBankTransactionDao getDaoObject() {
        return new BankTransactionDaoImpl();
    }

    public BankTransactionController() throws InstantiationException, IllegalAccessException {
        super();
    }

    @Override
    public List<BankTransaction> getAccountBankTransactions(Date startDate, Date endDate,
            Integer accountId) throws InvalidParameterException,
            IllegalAccessException, InstantiationException, SQLException {

        List<BankTransaction> list = null;

        if (accountId == null) {
            throw new InvalidParameterException("null accountId");
        }
        List<BankTransaction> accountTransactios = getDaoObject().findByAccountId(accountId);

        if (accountTransactios != null) {
            list = new ArrayList<>();
            for (BankTransaction tx : accountTransactios) {
                if (startDate != null && tx.getTimeStamp().after(startDate)) {
                    list.add(tx);
                }
                if (endDate != null && tx.getTimeStamp().before(endDate)) {
                    list.add(tx);
                }
            }
        }
        return list;
    }

    @Override
    public List<BankTransaction> getCustomerBankTransactions(
            Integer customerId) throws InstantiationException, SQLException,
            InvalidParameterException, IllegalAccessException {
        List<BankTransaction> list = new ArrayList<>();

        try {
            List<BankTransaction> allTransList = getAll();
            List<Integer> customerAccountsList =
                    new BankAccountController().getCustomerAccountsIds(customerId);
            for (BankTransaction transaction : allTransList) {
                if (customerAccountsList.contains(transaction.getAccountId())) {
                    list.add(transaction);
                }
            }
        } catch (SQLException | InstantiationException | IllegalAccessException | InvalidParameterException ex) {
            logger.error(ex);
            throw new InstantiationException(BankTransactionController.class
                    + " getBankTransactionByAccount (init BankAccountController) exception:" + ex);
        }
        return list;
    }

    @Override
    public void removeCustomerBankTransactions(Integer customerId) throws SQLException,
            InstantiationException, InvalidParameterException, IllegalAccessException {
        List<BankTransaction> customerTransactions = getCustomerBankTransactions(customerId);
        for (BankTransaction tx : customerTransactions) {
            remove(tx.getTxId());
        }
    }

    @Override
    public void withdraw(
            BigInteger amount,
            Integer currencyCode,
            String description,
            Integer accountId)
            throws SQLException, InvalidParameterException,
            AccountNotFoundException,
            IllegalAccountTypeException, InsufficientFundsException, InsufficientCreditException,
            InstantiationException, IllegalAccessException,
            TransactionTypeNotFoundException {

        BankAccount account = checkAccountArgs(amount, accountId);
        //new BankAccountController().getDetails(accountId);
        String type = account.getAccountType();
        if (DomainUtil.isCreditAccount(type)) {
            throw new IllegalAccountTypeException(type);
        }
        BigInteger newBalance = account.getBalance().subtract(amount);

        if (newBalance.compareTo(BigInteger.ZERO) == -1) {
            throw new InsufficientFundsException();
        }
        executeBankTransaction(BankTransactionTypeEnum.Withdraw,
                amount.negate(), currencyCode, description, newBalance, accountId, 0);
    }

    @Override
    public void makeCharge(
            BigInteger amount,
            Integer currencyCode,
            String description,
            Integer accountId)
            throws InvalidParameterException, AccountNotFoundException,
            IllegalAccountTypeException, InsufficientCreditException,
            InstantiationException, IllegalAccessException,
            SQLException, TransactionTypeNotFoundException {
        BankAccount account = checkAccountArgs(amount, accountId);
        //add money to the account
        BigInteger oldBalance = account.getBalance();
        BigInteger newBalance = oldBalance.add(amount);
        executeBankTransaction(BankTransactionTypeEnum.MakeCharge,
                amount, currencyCode, description, newBalance, accountId, 0);
    }

    @Override
    public void makePayment(BigInteger amount, Integer currencyCode, String description, Integer accountId)
            throws SQLException, InvalidParameterException, InstantiationException,
            AccountNotFoundException, IllegalAccessException,
            InsufficientFundsException, InsufficientCreditException,
            TransactionTypeNotFoundException {
        //checkAccountArgs(amount, description, accountId);
        BankAccountController accountBean = new BankAccountController();
        BankAccount account = accountBean.getDetails(accountId);

        //Check account type
        //String type = account.getAccountType();
        //if (!DomainUtil.isCreditAccount(type)) {
        //     throw new IllegalAccountTypeException(type);

        BigInteger newBalance = account.getBalance().subtract(amount);
        account.setBalance(newBalance);
        accountBean.update(account);
        //write transaction
        executeBankTransaction(BankTransactionTypeEnum.MakePayment,
                amount, currencyCode, description, newBalance, accountId, 0);
    }

    @Override
    public void transferFunds(
            BigInteger amount,
            Integer currencyCode,
            String description,
            Integer fromAccountId,
            Integer toAccountId)
            throws SQLException, InvalidParameterException, InstantiationException,
            AccountNotFoundException, IllegalAccessException,
            InsufficientFundsException, InsufficientCreditException,
            TransactionTypeNotFoundException {

        BankAccount fromAccount = checkAccountArgs(amount, fromAccountId);
        BankAccount toAccount = checkAccountArgs(amount, toAccountId);
        String fromType = fromAccount.getAccountType();
        BigInteger fromBalance = fromAccount.getBalance();

        final Integer currencyFrom = fromAccount.getCurrencyId();
        final Integer currencyTo = toAccount.getCurrencyId();
        
        if (!currencyFrom.equals(currencyTo)) {
            throw new InvalidParameterException("Base and target accounts currencies are different!");
        }
        if (fromAccountId.equals(toAccountId)) {
            throw new InvalidParameterException("Base and target account is the same!");
        }
        
        BigInteger fromNewBalance = fromBalance.subtract(amount);
        //if (fromNewBalance.compareTo(BigInteger.ZERO) == -1) {
        //    throw new InsufficientFundsException();
        //}
        executeBankTransaction(BankTransactionTypeEnum.TransferFunds,
                amount.negate(), currencyCode, description, fromNewBalance, fromAccountId, toAccountId);

    }

    // private methods
    private void executeBankTransaction(
            BankTransactionTypeEnum txTypeEnum,
            BigInteger amount,
            Integer currencyCode,
            String description,
            BigInteger newBalance,
            Integer fromAccountId,
            Integer toAccountId)
            throws SQLException, InvalidParameterException,
            InstantiationException, IllegalAccessException,
            TransactionTypeNotFoundException {

        //Find type in DB
        BankTransactionType txType = new BankTransactionTypeController().getByName(txTypeEnum.toString());
        Integer txTypeId = txType.getTxTypeId();

        // Set the new balance to the account
        BankAccountController accountBean = new BankAccountController();
        BankAccount account = accountBean.getDetails(fromAccountId);
        account.setBalance(newBalance);

        if (accountBean.update(account)) {
            //if account balanc changed then create a new transaction record
            BankTransaction transaction =
                    new BankTransaction(txTypeId, currencyCode, fromAccountId, newBalance, amount, description);
            if (toAccountId > 0) {
                transaction.setTargetAccountId(toAccountId);
                BankAccount targetAccount = accountBean.getDetails(fromAccountId);
                String toType = targetAccount.getAccountType();
                BigInteger toBalance = targetAccount.getBalance();
                if (DomainUtil.isCreditAccount(toType)) {
                    BigInteger toNewBalance = toBalance.add(amount);
                    targetAccount.setBalance(toNewBalance);
                    accountBean.update(targetAccount);//update in DB
                }
            }
            //Save this transaction
            create(transaction);
        }
    }

    private BankAccount checkAccountArgs(BigInteger amount, Integer accountId)
            throws InvalidParameterException, AccountNotFoundException,
            InstantiationException, InstantiationException, IllegalAccessException {
        if (accountId == null) {
            throw new InvalidParameterException("null accountId");
        }
        BankAccount account = new BankAccountController().getDetails(accountId);
        if (account.getBalance().subtract(amount).longValue() < 0) {
            throw new InvalidParameterException("Available balance amount acceded ");
        }
        return account;
    }

    public void createTransaction(String txTypeName, BigInteger amount, Integer currencyCode,
            String description,
            Integer baseAccountId, Integer targeAccountId)
            throws InvalidParameterException, IllegalAccountTypeException, InstantiationException,
            AccountNotFoundException, IllegalAccessException,
            InsufficientCreditException, InsufficientFundsException,
            TransactionFailedException, TransactionTypeNotFoundException {

        BankTransactionTypeEnum txTypeEnum = BankTransactionTypeEnum.parseString(txTypeName);
        try {
            if (txTypeEnum == BankTransactionTypeEnum.TransferFunds) {
                transferFunds(amount, currencyCode, description, baseAccountId, targeAccountId);
            } else if (txTypeEnum == com.bionic.banking.core.enums.BankTransactionTypeEnum.Withdraw) {
                withdraw(amount, currencyCode, description, baseAccountId);
            } else if (txTypeEnum == com.bionic.banking.core.enums.BankTransactionTypeEnum.MakePayment) {
                makePayment(amount, currencyCode, description, baseAccountId);
            } else if (txTypeEnum == com.bionic.banking.core.enums.BankTransactionTypeEnum.MakeCharge) {
                makeCharge(amount, currencyCode, description, baseAccountId);
            }
        } catch (SQLException ex) {
            logger.error(ex);
            throw new TransactionFailedException(BankTransactionController.class
                    + " createTransaction failed"
                    + ex.getMessage());
        }
    }
}
